package com.ihr360.expr.engine;

import com.ihr360.expr.ExprException;
import com.ihr360.expr.ExprMissing;
import com.ihr360.expr.context.IEvaluationContext;
import com.ihr360.expr.core.Expr;
import com.ihr360.expr.core.ExprEvaluatable;
import com.ihr360.expr.core.ExprFunction;
import com.ihr360.expr.core.ExprVariable;
import com.ihr360.expr.parser.ExprLexer;
import com.ihr360.expr.parser.ExprParser;
import com.ihr360.expr.parser.IParserVisitor;
import com.ihr360.expr.utils.ExprUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;

public abstract class AbstractCalculationEngine implements IParserVisitor,
        IEvaluationContext {
    final Logger logger = LoggerFactory.getLogger(AbstractCalculationEngine.class.getName());
    protected Map<VariableID, String> rawInputs = new HashMap();

    protected EngineProvider provider;

    protected ExprMissing MISSING = new ExprMissing();
    protected boolean autoCalculate = true;
    //这个应该可以不使用
    protected Map<String, VariableID> aliases = new TreeMap();

    protected Map<VariableID, Expr> inputs = new HashMap();

    private Map<VariableID, Expr> values = new HashMap();

    protected String namespace;

    public AbstractCalculationEngine(EngineProvider provider) {
        this.provider = provider;
    }

    public EngineProvider getProvider() {
        return provider;
    }

    public void setAutoCalculate(boolean auto) {
        this.autoCalculate = auto;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public void set(String name, String expression) throws ExprException {
        set(VariableID.valueOf(name), expression);
    }

    protected abstract void set(VariableID variableID, String expression)
            throws ExprException;


    public Set<VariableID> getInputVariables() {
        return rawInputs.keySet();
    }


    public Expr getValue(VariableID variableID) {
        Expr value = getEvaluatedValue(variableID);
        if (value == null && provider != null) {
            try {
                value = provider.getVariable(variableID);
                if (value != null && value instanceof ExprEvaluatable) {
                    value = ((ExprEvaluatable) value).evaluate(this);
                }
            } catch (ExprException e) {
                logger.error(e.getMessage(), e);
            }
        }
        return value;
    }

    public String getValueAsString(VariableID variableID) {
        Expr expr = getValue(variableID);
        String value = null;
        if (expr != null) {
            try {
                value = ExprUtils.convertExprAsStringValue(expr);
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
            }
        }
        return value;
    }


    public abstract void calculate(boolean force) throws ExprException;

    @Override
    public void annotateFunction(ExprFunction function) throws ExprException {
    }

    @Override
    public void annotateVariable(ExprVariable variable) throws ExprException {
        VariableID r = null;

        r = VariableID.valueOf(variable.getName());

        variable.setAnnotation(r);
    }

    @Override
    public Expr evaluateFunction(ExprFunction function) throws ExprException {
        return provider.evaluateFunction(this, function);
    }

    @Override
    public Expr evaluateVariable(ExprVariable variable) throws ExprException {
        VariableID variableID = (VariableID) variable.getAnnotation();
        if (variableID == null) {
            provider.validate(variable);
            return MISSING;
        }

        String sheet = variableID.getSheet();
        String ns = variableID.getNamespace();

        if (ns != null && !ns.equals(namespace) || sheet != null) {
            Expr e = provider.evaluateVariable(this, variable);
            if (e != null) {
                if (logger.isDebugEnabled()){
                    logger.debug("provider variable {}={}", variable.getName(), e.toString());
                }
                if (e instanceof ExprEvaluatable) {
                    e = ((ExprEvaluatable) e).evaluate(this);
                }
            } else {
                if (logger.isDebugEnabled()){
                    logger.info("provider variable {}=null", variable.getName());
                }
            }

            if (e == null) {
                e = this.provider.notifyMissing(variable);
                //e = MISSING;
            }
            return e;
        }
        Expr e = getEvaluatedValue(variableID);
        if (e == null) {
            // TODO: think about external variables versus missing
            e = provider.evaluateVariable(null, variable);
            if (e == null) {
                e = this.provider.notifyMissing(variable);
            }
        }
        return e;

    }

    private Expr getEvaluatedValue(VariableID variableID) {
        if (provider.isValueKeeper()) {
            return provider.getValue(variableID);
        }
        return values.get(variableID);
    }

    protected void addEvaluatedValue(VariableID variableId, Expr value) {
        if (provider.isValueKeeper()) {
            provider.putValue(variableId, value);
            return;
        }
        values.put(variableId, value);
    }

    protected void removeEvaluatedValue(VariableID variableID) {
        if (provider.isValueKeeper()) {
            provider.removeValue(variableID);
            return;
        }
    }


    protected Expr parseExpression(VariableID variableID, String expression) throws ExprException {
        Expr result;
        if (!expression.startsWith("=")) {
            result = ExprUtils.parseValue(expression);
        } else {
            expression = expression.substring(1);
            ExprParser p = new ExprParser();
            p.setParserVisitor(this);
            try {
                p.parse(new ExprLexer(expression));
            } catch (ExprException e) {
                throw new ExprException(variableID.getVariableId(), expression, e);
            } catch (IOException e) {
                throw new ExprException(variableID.getVariableId(), expression, e);
            }
            result = p.get();
        }
        return result;
    }
}